﻿namespace JavaScript8000.MagicBox
{
    using System;
    using System.Collections.Generic;
    using System.Text;

    using JavaScript8000.Common;
    using JavaScript8000.Scaner;

    /// <summary>
    /// Represent table of user variables.
    /// </summary>
    public class VVarTable
    {
        #region Constants and Fields

        /// <summary>
        /// Dictionary of variables.
        /// </summary>
        private readonly Dictionary<int, VVar> table;

        /// <summary>
        /// Max code in table.
        /// </summary>
        private int maxTempCode;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="VVarTable"/> class.
        /// </summary>
        public VVarTable()
        {
            this.table = new Dictionary<int, VVar>();
            this.maxTempCode = 9999999;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="VVarTable"/> class.
        /// </summary>
        /// <param name="scanResults">
        /// The scan results.
        /// </param>
        public VVarTable(ScanResults scanResults)
        {
            this.table = new Dictionary<int, VVar>();
            this.maxTempCode = 9999999;

            foreach (Token token in scanResults.Tokens)
            {
                if (token.UserCode != 0 && !this.table.ContainsKey(token.UserCode))
                {
                    // If it is a user token. Add all literals.

                    // bool assignable = token.Code != SystemTokens.NumericLiteralCode
                    // && token.Code != SystemTokens.StringLiteralCode;
                    var value = new VVarValue();
                    string name = scanResults.UserTokens.GetByUserCode(token.UserCode);

                    // Detect type for literals.
                    if (token.Code == SystemTokens.NumericLiteralCode)
                    {
                        value.Type = VVarValue.VVarType.Decimal;
                        value.Value = decimal.Parse(name);
                    }
                    else if (token.Code == SystemTokens.StringLiteralCode)
                    {
                        value.Type = VVarValue.VVarType.String;
                        value.Value = name.Substring(1, name.Length - 2);
                    }
                    else if (token.Code == SystemTokens.BoolLiteralCode)
                    {
                        value.Type = VVarValue.VVarType.Bool;
                        value.Value = bool.Parse(name);
                    }
                    else if (token.Code == SystemTokens.NullLiteral)
                    {
                        value.Type = VVarValue.VVarType.Unknown;
                        value.Value = null;
                    }
                    else
                    {
                        // value.Type = VVarValue.VVarType.Unknown;
                        continue;
                    }

                    this.table.Add(token.UserCode, new VVar { Assignable = false, Name = name, Value = value });
                }
            }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the parent.
        /// </summary>
        /// <value>
        /// The parent.
        /// </value>
        public VVarTable Parent { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Declares the variable.
        /// </summary>
        /// <param name="userCode">
        /// The user code.
        /// </param>
        /// <param name="userTokens">
        /// The user tokens.
        /// </param>
        /// <param name="value">
        /// The value of declared variable.
        /// </param>
        public void DeclareVariable(int userCode, UserTokens userTokens, VVarValue value)
        {
            if (this.table.ContainsKey(userCode))
            {
                throw new Exception(
                    string.Format("Variable{0} is already declared.", userTokens.GetByUserCode(userCode)));
            }

            this.table.Add(
                userCode,
                new VVar
                    {
                        Assignable = true,
                        Name = userTokens.GetByUserCode(userCode),
                        Value = value
                    });
        }

        /// <summary>
        /// Gets virtual variable by user code.
        /// </summary>
        /// <param name="code">
        /// The code.
        /// </param>
        /// <returns>
        /// </returns>
        public VVar GetVVar(int code)
        {
            if (this.table.ContainsKey(code))
            {
                return this.table[code];
            }

            if (this.Parent != null)
            {
                return this.Parent.GetVVar(code);
            }

            throw new ArgumentException("Trying to use unassigned variables.");
        }

        /// <summary>
        /// Temps the var.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// User code.
        /// </returns>
        public int TempVar(VVarValue value)
        {
            this.maxTempCode++;
            this.table.Add(
                this.maxTempCode,
                new VVar
                    {
                        Assignable = false,
                        Name = string.Format("TempVar{0}", this.maxTempCode - 9999999),
                        Value = value
                    });
            return this.maxTempCode;
        }

        /// <summary>
        /// Removes the temp var.
        /// </summary>
        /// <param name="code">The code.</param>
        public void RemoveTempVar(int code)
        {
            if (!this.table.ContainsKey(code))
            {
                //if (this.Parent != null)
                //{
                //    this.Parent.RemoveTempVar(code);
                //    return;
                //}

                throw new Exception("Can't remove variable which doesn't exist in table.");
            }

            this.table.Remove(code);
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            var sb = new StringBuilder();

            foreach (var keyValuePair in this.table)
            {
                sb.AppendLine(
                    string.Format(
                        "{0}\t\t{1}\t{2}\t\t{3}\t\t{4}",
                        keyValuePair.Value.Name,
                        keyValuePair.Key,
                        keyValuePair.Value.Value == null ? "[undefined]" : keyValuePair.Value.Value.Type.ToString(),
                        keyValuePair.Value.Value == null ? "[undefined]" : keyValuePair.Value.Value.Value,
                        keyValuePair.Value.Assignable ? "[assignable]" : "[not assignable]"));
            }

            return sb.ToString();
        }

        #endregion
    }
}